<!-- 
“关注点分离” (Separation of Concerns) 设计模式
GenericModal.vue 
  - 职责：可复用的 UI 组件
    - 处理动画、何根据 position 调整位置、如何显示/隐藏遮罩层
    - 通过 props 接收指令，通过 <slot> 接收要展示内容
    - 高复用性和表现与逻辑解耦
useModal / modalStore
  - 职责：服务/状态管理器，关心“什么时间，显示什么内容的弹框”
    - 维护一个需要显示的弹框列表 
    - 全局访问和逻辑集中管理
ModalProvider.vue 
  - 职责：维护UI和逻辑层的关系
    - 监听modalStore.modals 把GenericModal展示出来
-->

<template>
  <!-- 使用 Teleport 将弹框渲染到 body 下，避免父元素的 z-index 和 overflow 问题 -->
  <teleport to="body">
    <!-- 
      Vue 的 Transition 组件用于处理动画。
      :name 会匹配对应的 CSS 类名，如 fade-enter-active
      @after-leave 事件在关闭动画结束后触发，用于安全地卸载组件
    -->
    <transition
      :name="animationName"
      @after-enter="onAfterEnter"
      @after-leave="onAfterLeave"
    >
      <!-- v-show 控制显示/隐藏，而不是 v-if，这样可以让 transition 生效 -->
      <div
        v-show="isVisible"
        class="fixed inset-0 z-50 flex"
        :class="positionClasses"
        @keydown.esc="handleEsc"
      >
        <!-- 遮盖层 -->
        <div
          v-if="props.showOverlay"
          class="absolute inset-0"
          :style="{ backgroundColor: props.overlayColor }"
          @click="handleOverlayClick"
        ></div>

        <!-- 弹框内容容器 -->
        <div
          ref="modalContentRef"
          class="relative m-4"
          role="dialog"
          aria-modal="true"
          :style="offsetStyle"
        >
          <!-- 插槽，用于填充任意内容 -->
          <slot></slot>
        </div>
      </div>
    </transition>
  </teleport>
</template>

<script setup lang="ts">
import { ref, computed, watch, onMounted, onUnmounted, nextTick } from 'vue'

// --- 类型定义 ---
type Position = 'top' | 'center' | 'bottom' | { target: HTMLElement; offset: [number, number] }
type AnimationType = 'fade' | 'slide-up' | 'slide-down' | 'none'

// --- Props 定义 ---
const props = withDefaults(
  defineProps<{
    modelValue: boolean // v-model 控制显示/隐藏
    animationType?: AnimationType
    position?: Position
    showOverlay?: boolean
    overlayColor?: string
    closeOnClickOverlay?: boolean
    duration?: number
  }>(),
  {
    animationType: 'fade',
    position: 'center',
    showOverlay: true,
    overlayColor: 'rgba(0, 0, 0, 0.5)',
    closeOnClickOverlay: true,
    duration: undefined,
  }
)

// --- Emits 定义 ---
const emit = defineEmits<{
  (e: 'update:modelValue', value: boolean): void
  (e: 'show'): void
  (e: 'hide'): void
  (e: 'afterShow'): void
  (e: 'afterHide'): void
}>()

// --- 内部状态 ---
const isVisible = ref(false) // 内部可见性状态，用于驱动 Transition
const modalContentRef = ref<HTMLElement | null>(null)
const offsetStyle = ref({})
const timerId = ref<number | null>(null) 

// --- 计算属性 ---

// 根据 animationType prop 生成 Transition 组件的 name
const animationName = computed(() => {
  if (props.animationType === 'none') return ''
  return props.animationType
})

// 根据 position prop 生成 Tailwind CSS 类
const positionClasses = computed(() => {
  if (typeof props.position === 'object') {
    return 'items-start justify-start' // 使用绝对定位，对齐方式不重要
  }
  switch (props.position) {
    case 'top':
      return 'items-start justify-center pt-10'
    case 'bottom':
      return 'items-end justify-center pb-10'
    case 'center':
    default:
      return 'items-center justify-center'
  }
})

// --- 方法 ---

const show = () => {
  if (isVisible.value) return
  isVisible.value = true
  emit('update:modelValue', true)
  emit('show')
  
  // 处理基于元素的定位
  if (typeof props.position === 'object' && props.position.target) {
    nextTick(() => calculateOffsetPosition())
  }
}

const hide = () => {
  if (!isVisible.value) return
  if (timerId.value) {
    clearTimeout(timerId.value)
    timerId.value = null
  }
  isVisible.value = false
  emit('update:modelValue', false)
  emit('hide')
}

const handleOverlayClick = () => {
  if (props.closeOnClickOverlay) {
    hide()
  }
}

const handleEsc = (event: KeyboardEvent) => {
  if (event.key === 'Escape') {
    hide() // 只有是 Escape 键时才调用 hide()
  }
}

const calculateOffsetPosition = () => {
  const pos = props.position
  if (typeof pos !== 'object' || !pos.target || !modalContentRef.value) return

  const targetRect = pos.target.getBoundingClientRect()
  const modalRect = modalContentRef.value.getBoundingClientRect()
  const [offsetX, offsetY] = pos.offset

  // 计算位置，这里简单示例为右下角偏移
  // 你可以根据需求实现更复杂的定位逻辑，例如上、下、左、右
  let top = targetRect.bottom + offsetY
  let left = targetRect.left + offsetX

  // 防止超出视窗边界
  if (left + modalRect.width > window.innerWidth) {
    left = window.innerWidth - modalRect.width - 16 // 16px margin
  }
  if (top + modalRect.height > window.innerHeight) {
    top = window.innerHeight - modalRect.height - 16
  }

  offsetStyle.value = {
    position: 'absolute',
    top: `${top}px`,
    left: `${left}px`,
  }
}

// --- 生命周期与侦听器 ---

watch(
  () => props.modelValue,
  (newValue) => {
    if (newValue) {
      show()
    } else {
      hide()
    }
  },
)

const onAfterEnter = () => {
  emit('afterShow')
  // 当进入动画结束后，检查是否需要自动关闭
  if (props.duration && props.duration > 0) {
    timerId.value = window.setTimeout(() => {
      hide()
    }, props.duration)
  }
}
const onAfterLeave = () => emit('afterHide')

onMounted(() => {
  // 当组件挂载后，检查 modelValue prop。
  // 如果为 true（通常是命令式调用的情况），则调用 show()。
  // 因为 onMounted 在初始渲染之后执行，所以此时改变 isVisible 会被 <transition> 捕获。
  if (props.modelValue) {
    show()
  }
  document.addEventListener('keydown', handleEsc)
})

onUnmounted(() => {
  if (timerId.value) {
    clearTimeout(timerId.value)
  }
  document.removeEventListener('keydown', handleEsc)
})

// --- 暴露方法 ---
// 允许父组件通过 ref 调用 show/hide 方法
defineExpose({ show, hide })
</script>

<style>
/* 动画效果：淡入淡出 */
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.3s ease;
}
.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}

/* 动画效果：向上滑动 */
.slide-up-enter-active,
.slide-up-leave-active {
  transition: all 0.3s ease;
}
.slide-up-enter-from,
.slide-up-leave-to {
  opacity: 0;
  transform: translateY(100%);
}

/* 动画效果：向下滑动 */
.slide-down-enter-active,
.slide-down-leave-active {
  transition: all 0.3s ease;
}
.slide-down-enter-from,
.slide-down-leave-to {
  opacity: 0;
  transform: translateY(-100%);
}
</style>
